/* -*- C++ -*-
 * Copyright 2019-2020 LibRaw LLC (info@libraw.org)
 *
 LibRaw uses code from dcraw.c -- Dave Coffin's raw photo decoder,
 dcraw.c is copyright 1997-2018 by Dave Coffin, dcoffin a cybercom o net.
 LibRaw do not use RESTRICTED code from dcraw.c

 LibRaw is free software; you can redistribute it and/or modify
 it under the terms of the one of two licenses as you choose:

1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
   (See file LICENSE.LGPL provided in LibRaw distribution archive for details).

2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
   (See file LICENSE.CDDL provided in LibRaw distribution archive for details).

 */

#include "../../internal/dcraw_defs.h"

void LibRaw::convert_to_rgb()
{
  int i, j, k;
  size_t prof_desc_len;
  char *prof_desc;
  float out_cam[3][4];
  double num, inverse[3][3];
  static const double(*out_rgb[])[3] = {
      LibRaw_constants::rgb_rgb,  LibRaw_constants::adobe_rgb,
      LibRaw_constants::wide_rgb, LibRaw_constants::prophoto_rgb,
      LibRaw_constants::xyz_rgb,  LibRaw_constants::aces_rgb};
  static const char *name[] = {"sRGB",          "Adobe RGB (1998)",
                               "WideGamut D65", "ProPhoto D65",
                               "XYZ",           "ACES"};
  static const unsigned phead[] = {
      1024, 0, 0x2100000,  0x6d6e7472, 0x52474220, 0x58595a20, 0,
      0,    0, 0x61637370, 0,          0,          0x6e6f6e65, 0,
      0,    0, 0,          0xf6d6,     0x10000,    0xd32d};
  unsigned pbody[] = {10,         0x63707274, 0,  36, /* cprt */
                      0x64657363, 0,          60,     /* desc, len is strlen(longest_string) + 12 */
                      0x77747074, 0,          20,     /* wtpt */
                      0x626b7074, 0,          20,     /* bkpt */
                      0x72545243, 0,          14,     /* rTRC */
                      0x67545243, 0,          14,     /* gTRC */
                      0x62545243, 0,          14,     /* bTRC */
                      0x7258595a, 0,          20,     /* rXYZ */
                      0x6758595a, 0,          20,     /* gXYZ */
                      0x6258595a, 0,          20};    /* bXYZ */
  static const unsigned pwhite[] = {0xf351, 0x10000, 0x116cc};
  unsigned pcurve[] = {0x63757276, 0, 1, 0x1000000};

  RUN_CALLBACK(LIBRAW_PROGRESS_CONVERT_RGB, 0, 2);

  prof_desc_len = snprintf(NULL, 0, "%s gamma %g toe slope %g", name[output_color - 1], floorf(1000.f/gamm[0]+.5f)/1000.f, floorf(gamm[1]*1000.0f+.5f)/1000.f) + 1;
  prof_desc = (char *)malloc(prof_desc_len);
  sprintf(prof_desc, "%s gamma %g toe slope %g", name[output_color - 1], floorf(1000.f/gamm[0]+.5f)/1000.f, floorf(gamm[1]*1000.0f+.5f)/1000.f);
//  printf ("==>> desc: =%s=\n", prof_desc);

  gamma_curve(gamm[0], gamm[1], 0, 0);
  memcpy(out_cam, rgb_cam, sizeof out_cam);
  raw_color |= colors == 1 || output_color < 1 || output_color > 6;
  if (!raw_color)
  {
    oprof = (unsigned *)calloc(phead[0], 1);
    merror(oprof, "convert_to_rgb()");
    memcpy(oprof, phead, sizeof phead);
    if (output_color == 5)
      oprof[4] = oprof[5];
    oprof[0] = 132 + 12 * pbody[0];
    for (i = 0; i < pbody[0]; i++)
    {
      oprof[oprof[0] / 4] = i ? (i > 1 ? 0x58595a20 : 0x64657363) : 0x74657874;
      pbody[i * 3 + 2] = oprof[0];
      oprof[0] += (pbody[i * 3 + 3] + 3) & -4;
    }
    memcpy(oprof + 32, pbody, sizeof pbody);
    oprof[pbody[5] / 4 + 2] = prof_desc_len + 1;
    memcpy((char *)oprof + pbody[8] + 8, pwhite, sizeof pwhite);
    pcurve[3] = (short)(256 / gamm[5] + 0.5) << 16;
    for (i = 4; i < 7; i++)
      memcpy((char *)oprof + pbody[i * 3 + 2], pcurve, sizeof pcurve);
    pseudoinverse((double(*)[3])out_rgb[output_color - 1], inverse, 3);
    for (i = 0; i < 3; i++)
      for (j = 0; j < 3; j++)
      {
        for (num = k = 0; k < 3; k++)
          num += LibRaw_constants::xyzd50_srgb[i][k] * inverse[j][k];
        oprof[pbody[j * 3 + 23] / 4 + i + 2] = num * 0x10000 + 0.5;
      }
    for (i = 0; i < phead[0] / 4; i++)
      oprof[i] = htonl(oprof[i]);
    strcpy((char *)oprof + pbody[2] + 8, "auto-generated by dcraw");
    strcpy((char *)oprof + pbody[5] + 12, prof_desc);
    for (i = 0; i < 3; i++)
      for (j = 0; j < colors; j++)
        for (out_cam[i][j] = k = 0; k < 3; k++)
          out_cam[i][j] += out_rgb[output_color - 1][i][k] * rgb_cam[k][j];
  }
  convert_to_rgb_loop(out_cam);

  if (colors == 4 && output_color)
    colors = 3;

  RUN_CALLBACK(LIBRAW_PROGRESS_CONVERT_RGB, 1, 2);
}

void LibRaw::scale_colors()
{
  unsigned bottom, right, size, row, col, ur, uc, i, x, y, c, sum[8];
  int val, dark, sat;
  double dsum[8], dmin, dmax;
  float scale_mul[4], fr, fc;
  ushort *img = 0, *pix;

  RUN_CALLBACK(LIBRAW_PROGRESS_SCALE_COLORS, 0, 2);

  if (user_mul[0])
    memcpy(pre_mul, user_mul, sizeof pre_mul);
  if (use_auto_wb || (use_camera_wb && cam_mul[0] <= 0.00001f))
  {
    memset(dsum, 0, sizeof dsum);
    bottom = MIN(greybox[1] + greybox[3], height);
    right = MIN(greybox[0] + greybox[2], width);
    for (row = greybox[1]; row < bottom; row += 8)
      for (col = greybox[0]; col < right; col += 8)
      {
        memset(sum, 0, sizeof sum);
        for (y = row; y < row + 8 && y < bottom; y++)
          for (x = col; x < col + 8 && x < right; x++)
            FORC4
            {
              if (filters)
              {
                c = fcol(y, x);
                val = BAYER2(y, x);
              }
              else
                val = image[y * width + x][c];
              if (val > maximum - 25)
                goto skip_block;
              if ((val -= cblack[c]) < 0)
                val = 0;
              sum[c] += val;
              sum[c + 4]++;
              if (filters)
                break;
            }
        FORC(8) dsum[c] += sum[c];
      skip_block:;
      }
    FORC4 if (dsum[c]) pre_mul[c] = dsum[c + 4] / dsum[c];
  }
  if (use_camera_wb && cam_mul[0] > 0.00001f)
  {
    memset(sum, 0, sizeof sum);
    for (row = 0; row < 8; row++)
      for (col = 0; col < 8; col++)
      {
        c = FC(row, col);
        if ((val = white[row][col] - cblack[c]) > 0)
          sum[c] += val;
        sum[c + 4]++;
      }
    if (imgdata.color.as_shot_wb_applied)
    {
      // Nikon sRAW: camera WB already applied:
      pre_mul[0] = pre_mul[1] = pre_mul[2] = pre_mul[3] = 1.0;
    }
    else if (sum[0] && sum[1] && sum[2] && sum[3])
      FORC4 pre_mul[c] = (float)sum[c + 4] / sum[c];
    else if (cam_mul[0] > 0.00001f && cam_mul[2] > 0.00001f)
      memcpy(pre_mul, cam_mul, sizeof pre_mul);
    else
    {
      imgdata.process_warnings |= LIBRAW_WARN_BAD_CAMERA_WB;
    }
  }
  // Nikon sRAW, daylight
  if (imgdata.color.as_shot_wb_applied && !use_camera_wb && !use_auto_wb &&
      cam_mul[0] > 0.00001f && cam_mul[1] > 0.00001f && cam_mul[2] > 0.00001f)
  {
    for (c = 0; c < 3; c++)
      pre_mul[c] /= cam_mul[c];
  }
  if (pre_mul[1] == 0)
    pre_mul[1] = 1;
  if (pre_mul[3] == 0)
    pre_mul[3] = colors < 4 ? pre_mul[1] : 1;
  dark = black;
  sat = maximum;
  if (threshold)
    wavelet_denoise();
  maximum -= black;
  for (dmin = DBL_MAX, dmax = c = 0; c < 4; c++)
  {
    if (dmin > pre_mul[c])
      dmin = pre_mul[c];
    if (dmax < pre_mul[c])
      dmax = pre_mul[c];
  }
  if (!highlight)
    dmax = dmin;
  if (dmax > 0.00001 && maximum > 0)
    FORC4 scale_mul[c] = (pre_mul[c] /= dmax) * 65535.0 / maximum;
  else
    FORC4 scale_mul[c] = 1.0;

  if (filters > 1000 && (cblack[4] + 1) / 2 == 1 && (cblack[5] + 1) / 2 == 1)
  {
    FORC4 cblack[FC(c / 2, c % 2)] +=
        cblack[6 + c / 2 % cblack[4] * cblack[5] + c % 2 % cblack[5]];
    cblack[4] = cblack[5] = 0;
  }
  size = iheight * iwidth;
  scale_colors_loop(scale_mul);
  if ((aber[0] != 1 || aber[2] != 1) && colors == 3)
  {
    for (c = 0; c < 4; c += 2)
    {
      if (aber[c] == 1)
        continue;
      img = (ushort *)malloc(size * sizeof *img);
      merror(img, "scale_colors()");
      for (i = 0; i < size; i++)
        img[i] = image[i][c];
      for (row = 0; row < iheight; row++)
      {
        ur = fr = (row - iheight * 0.5) * aber[c] + iheight * 0.5;
        if (ur > iheight - 2)
          continue;
        fr -= ur;
        for (col = 0; col < iwidth; col++)
        {
          uc = fc = (col - iwidth * 0.5) * aber[c] + iwidth * 0.5;
          if (uc > iwidth - 2)
            continue;
          fc -= uc;
          pix = img + ur * iwidth + uc;
          image[row * iwidth + col][c] =
              (pix[0] * (1 - fc) + pix[1] * fc) * (1 - fr) +
              (pix[iwidth] * (1 - fc) + pix[iwidth + 1] * fc) * fr;
        }
      }
      free(img);
    }
  }
  RUN_CALLBACK(LIBRAW_PROGRESS_SCALE_COLORS, 1, 2);
}

// green equilibration
void LibRaw::green_matching()
{
  int i, j;
  double m1, m2, c1, c2;
  int o1_1, o1_2, o1_3, o1_4;
  int o2_1, o2_2, o2_3, o2_4;
  ushort(*img)[4];
  const int margin = 3;
  int oj = 2, oi = 2;
  float f;
  const float thr = 0.01f;
  if (half_size || shrink)
    return;
  if (FC(oj, oi) != 3)
    oj++;
  if (FC(oj, oi) != 3)
    oi++;
  if (FC(oj, oi) != 3)
    oj--;

  img = (ushort(*)[4])calloc(height * width, sizeof *image);
  merror(img, "green_matching()");
  memcpy(img, image, height * width * sizeof *image);

  for (j = oj; j < height - margin; j += 2)
    for (i = oi; i < width - margin; i += 2)
    {
      o1_1 = img[(j - 1) * width + i - 1][1];
      o1_2 = img[(j - 1) * width + i + 1][1];
      o1_3 = img[(j + 1) * width + i - 1][1];
      o1_4 = img[(j + 1) * width + i + 1][1];
      o2_1 = img[(j - 2) * width + i][3];
      o2_2 = img[(j + 2) * width + i][3];
      o2_3 = img[j * width + i - 2][3];
      o2_4 = img[j * width + i + 2][3];

      m1 = (o1_1 + o1_2 + o1_3 + o1_4) / 4.0;
      m2 = (o2_1 + o2_2 + o2_3 + o2_4) / 4.0;

      c1 = (abs(o1_1 - o1_2) + abs(o1_1 - o1_3) + abs(o1_1 - o1_4) +
            abs(o1_2 - o1_3) + abs(o1_3 - o1_4) + abs(o1_2 - o1_4)) /
           6.0;
      c2 = (abs(o2_1 - o2_2) + abs(o2_1 - o2_3) + abs(o2_1 - o2_4) +
            abs(o2_2 - o2_3) + abs(o2_3 - o2_4) + abs(o2_2 - o2_4)) /
           6.0;
      if ((img[j * width + i][3] < maximum * 0.95) && (c1 < maximum * thr) &&
          (c2 < maximum * thr))
      {
        f = image[j * width + i][3] * m1 / m2;
        image[j * width + i][3] = f > 0xffff ? 0xffff : f;
      }
    }
  free(img);
}
